from typing import TypedDict
from collections import namedtuple
from dataclasses import dataclass
from django.db.models import DecimalField

from typing import Any, Dict, Optional, Union, List, Tuple
from django.conf import settings
from django.db.models.query import QuerySet
from django.shortcuts import reverse
import stripe

from product import models
import product.utils.order as order
import product.utils.utils as product_utils


@dataclass
class ProductOptions:
    """ Определяем параметры для продукта """

    id: str
    price: Union[DecimalField, int]
    currency: Optional[str]

    def __post_init__(self):
        self.price = int(self.price) * 100


@dataclass
class CustomPaymentWithPaymentIntent:
    """ Пользовательский платеж с помощью PaymentIntent """

    price: Union[DecimalField, int]
    currency: str

    def __post_init__(self):
        self.price = int(self.price) * 100


class WorkWithApiStripe:
    """ Работаем с API сервиса Strip """

    def __init__(self):
        stripe.api_key = settings.STRIPE_SECRET_KEY


    def create_product(self, name: str) -> str:
        """ Создаем продукт и возвращаем его """

        return stripe.Product.create(name=name).id


    def create_session_to_pay_for_selected_products(
            self,
            order_options: order.OrderOptions,
            ) -> Union[Dict[str, str], str]:
        """ Создаем сессию для оплаты выбранных продуктов """

        content_of_line_items = order.OrderUtils().add_product_to_order_payment_session(
            order_options.product,
            order_options.list_of_products
        )

        data = order.OrderUtils().add_additional_options_to_order(
            content_of_line_items,
            order_options.discount,
            order_options.tax_rates
        )

        checkout_session = stripe.checkout.Session.create(
            line_items=content_of_line_items,
            mode='payment',
            discounts=data['discount_on_order'],
            success_url='http://localhos:8000' + reverse(
                'product:result_of_buy') + '?successful_buy=true',
            cancel_url='http://localhos:8000' + reverse(
                'product:result_of_buy') + '?/successful_buy=false'
        )

        if order_options.list_of_products:
            order.OrderUtils().make_order_complete(
                order.OrderUtils().get_last_order(),
                order_options.list_of_products
            )

        return checkout_session.id


    def get_different_structure_of_item_list(
            self,
            data: Dict[str, Any],
            main_item_name: str,
            list_of_fields: Tuple[str]) -> Tuple[Union[str, int]]:
        """ Получаем другую структуру списка элементов """

        result = []
        mainItem = namedtuple(main_item_name.title(), list_of_fields)

        for item in data['data']:

            list_of_values = []

            for field in list_of_fields:
                value = item[field]
                list_of_values.append(value)

            result.append(mainItem(*list_of_values))

        return result


class DiscountApiStripe(WorkWithApiStripe):
    """ Функционал для работы со скидками по API Stripe """

    def get_name_of_coupon_by_percent_off(self, percent_off: int) -> str:
        """ Получаем название купона по его процентной скидке """
        return f'{percent_off}% off'


    def get_coupon_id_by_percent_off(self, percent_off: int) -> str:
        """ Получаем id купона по его проценту """

        list_of_coupons = self.get_list_of_coupons()
        coupon_name = self.get_name_of_coupon_by_percent_off(percent_off)
        coupon = [coupon for coupon in list_of_coupons if coupon_name == coupon.name]

        if coupon: return coupon[0].id
        else: return False


    def get_list_of_coupons(
            self,
            another_structure: Optional[bool] = True
            ) -> Union[Tuple[Union[str, int]], Dict[str, Any]]:
        """ Получаем список купонов """

        data = stripe.Coupon.list()

        if another_structure:
            result = self.get_different_structure_of_item_list(
                data,
                'Coupon',
                ('id', 'name', 'percent_off')
            )
            return result
        else:
            return data


    def add_discount_to_order(self,
              order: models.Order,
              discount: int) -> None:
        """ Добавляем скидку заказу """

        discount_object, result = models.Discount.objects.get_or_create(
            percent_off=discount
        )
        order.discount = discount_object
        order.save()


    def create_coupon(self, percent_off: int) -> str:
        """ Создаем купон """

        coupon = stripe.Coupon.create(
            duration='once',
            percent_off=percent_off,
            name=f'{percent_off}% off'
        )
        return coupon.id


    def check_coupon_availability(self, percent_off: int) -> Optional[bool]:
        """ Проверяем наличие купона по его проценту """
        if self.get_coupon_id_by_percent_off(percent_off): return True


    def get_or_create_coupon(self, percent_off: int) -> str:

        if not self.check_coupon_availability(percent_off):
            return self.create_coupon(percent_off)
        else:
            return self.get_coupon_id_by_percent_off(percent_off)


class TaxRateApiStripe(WorkWithApiStripe):
    """ Функционал для работы с налоговыми ставками по API Stripe """

    def create_tax_rate(
            self,
            display_name: str,
            percentage: Union[int, float]) -> str:
        """ Создаем налоговую ставку """

        tax_rate = stripe.TaxRate.create(
            display_name=display_name,
            percentage=percentage,
            inclusive=False
        )
        return tax_rate['id']


    def get_list_of_tax_rates(
            self, another_structure: bool = True
            ) -> Union[Tuple[Union[str, int]], Dict[str, Any]]:
        """ Получаем список налоговых ставок """

        data = stripe.TaxRate.list()

        if another_structure:
            result = self.get_different_structure_of_item_list(
                data,
                'TaxRate',
                ('id', 'display_name', 'percentage')
            )
            return result
        else:
            return data


    def get_name_of_tax_rate_by_percent(self, percentage: int) -> str:
        """ Получаем название налоговой ставки по ее проценту """
        return f'{percentage}% some tax rate'


    def get_tax_rate_id(
        self,
        display_name: str,
        percentage: int) -> str:
        """ Получаем id налоговой ставки """

        list_of_tax_rates = self.get_list_of_tax_rates()

        for tax_rate in list_of_tax_rates:
            desired_name = display_name == tax_rate.display_name
            desired_percentage = percentage == tax_rate.percentage
            if desired_name and desired_percentage:
                return tax_rate.id


    def check_tax_rate_availability(
        self,
        display_name: str,
        percentage: int) -> Optional[bool]:
        """ Проверяем наличие налоговой ставки по ее проценту и названию """
        if self.get_tax_rate_id(display_name, percentage): return True


    def get_or_create_tax_rate(
        self,
        display_name: str,
        percentage: int) -> str:
        """ Получаем или создаем налоговую ставку """

        if not self.check_tax_rate_availability(display_name, percentage):
            return self.create_tax_rate(display_name, percentage)
        else:
            return self.get_tax_rate_id(display_name, percentage)


class PriceApiStripe(WorkWithApiStripe):
    """ Функционал для работы с ценой по API Stripe """

    def create_price(self, product_options: ProductOptions) -> str:
        """ Создаем цену и возвращаем ее id """

        default_currency = 'rub'
        price = stripe.Price.create(
            product=product_options.id,
            unit_amount=product_options.price,
            currency=default_currency,
        )
        return price.id


    def create_price_by_product_object(self, product: models.Product) -> str:
        """ Создаем цену по объекту продукта """

        product_id = self.create_product(product.name)
        product_options = ProductOptions(
            id=product_id,
            price=product.price,
            currency=product.currency

        )
        price_id = self.create_price(product_options)
        return price_id


    def get_price_id_list_from_list_of_products(
            self,
            list_of_products: QuerySet) -> List[str]:
        """ Получаем список price_id из списка продуктов """

        list_of_price_id = [self.create_price_by_product_object(product)
                            for product in list_of_products]
        return list_of_price_id


class PaymentIntentApiStripe(WorkWithApiStripe):
    """ Функционал для работы с PaymentIntent по API Stripe """

    def create_payment_intent(self, price: int, currency: str) -> str:
        """ Создаем объект PaymentIntent и возвращаем его """

        price_in_other_currencies = product_utils.ProductUtils().get_price_in_other_currencies(
            price,
            string_format=True
        )
        payment_intent = stripe.PaymentIntent.create(
          amount=price,
          currency=currency,
          automatic_payment_methods={'enabled': True},
          metadata={'price_in_other_currencies': price_in_other_currencies}
        )
        return payment_intent.client_secret


    def pay_for_product_payment_intent(
            self,
            custom_payment: CustomPaymentWithPaymentIntent) -> str:
        """ Оплачиваем продукт с помощью PaymentIntent """

        client_secret = self.create_payment_intent(
            price=custom_payment.price,
            currency=custom_payment.currency
        )
        return {'client_secret': client_secret}

